﻿#include "framelesswindow.h"
#include <QApplication>
#include <QPoint>
#include <QSize>
#include <QDebug>
#include <QDesktopWidget>

#ifdef Q_OS_WIN
#include <windows.h>
#include <WinUser.h>
#include <windowsx.h>
#include <dwmapi.h>
#include <objidl.h> // Fixes error C2504: 'IUnknown' : base class undefined
#include <gdiplus.h>
#include <GdiPlusColor.h>
#pragma comment (lib,"Dwmapi.lib") // Adds missing library, fixes error LNK2019: unresolved external symbol __imp__DwmExtendFrameIntoClientArea
#pragma comment (lib,"user32.lib")
#pragma comment (lib,"Gdi32.lib")   // 设置圆角用到gdi

CFramelessWindow::CFramelessWindow(QWidget *parent)
    : QMainWindow(parent),
      m_titlebar(Q_NULLPTR),
      m_borderWidth(5),
      m_bResizeable(true)
{
    //    setWindowFlag(Qt::Window,true);
    //    setWindowFlag(Qt::FramelessWindowHint, true);
    //    setWindowFlag(Qt::WindowSystemMenuHint, true);
    //    setWindowFlag() is not avaliable before Qt v5.9, so we should use setWindowFlags instead
    // 似乎要有Qt::WindowMaximizeButtonHint才能双击标题栏最大化和恢复不是最大化
    setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint /*| Qt::WindowMaximizeButtonHint*/ | Qt::WindowSystemMenuHint);

    setResizeable(m_bResizeable);
}

void CFramelessWindow::setResizeable(bool resizeable)
{
    bool visible = isVisible();
    m_bResizeable = resizeable;

    //this line will get titlebar/thick frame/Aero back, which is exactly what we want
    //we will get rid of titlebar and thick frame again in nativeEvent() later
    // WS_CAPTION会造成在win7出现原生的最小最大关闭按钮，win10正常
    HWND hwnd = (HWND)this->winId();
    DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
    //::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
    ::SetWindowLong(hwnd, GWL_STYLE, style & ~WS_CAPTION | WS_THICKFRAME | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);

    //保留一个像素的边框宽度，否则系统不会绘制边框阴影, 需要WS_THICKFRAME才会有阴影
    //we better left 1 piexl width of border untouch, so OS can draw nice shadow around it
    const MARGINS shadow = { 1, 1, 1, 1 };
    DwmExtendFrameIntoClientArea(HWND(winId()), &shadow);

    setVisible(visible);
}

void CFramelessWindow::setResizeableAreaWidth(int width)
{
    if (0 > width) width = 0;
    m_borderWidth = width;
}

void CFramelessWindow::setTitleBar(QWidget* titlebar)
{
    m_titlebar = titlebar;
    if (!titlebar) return;
    connect(titlebar, SIGNAL(destroyed(QObject*)), this, SLOT(onTitleBarDestroyed()));
}

void CFramelessWindow::onTitleBarDestroyed()
{
    if (m_titlebar == QObject::sender())
    {
        m_titlebar = Q_NULLPTR;
    }
}

void CFramelessWindow::addIgnoreWidget(QWidget* widget)
{
    if (!widget) return;
    if (m_whiteList.contains(widget)) return;
    m_whiteList.append(widget);
}

bool CFramelessWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
    // In your reimplementation of this function,
    // if you want to stop the event being handled by Qt, return true and set result.
    // The result parameter has meaning only on Windows.
    // If you return false, this native event is passed back to Qt, which translates the event into a Qt event and sends it to the widget.
    //Workaround for known bug -> check Qt forum : https://forum.qt.io/topic/93141/qtablewidget-itemselectionchanged/13
#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
    MSG* msg = *reinterpret_cast<MSG**>(message);
#else
    MSG* msg = reinterpret_cast<MSG*>(message);
#endif
    
    switch (msg->message)
    {
    case WM_NCCALCSIZE:
    {
        LPRECT pRect=NULL;

        if ( msg->wParam == TRUE)
        {
            LPNCCALCSIZE_PARAMS pParam = (LPNCCALCSIZE_PARAMS)msg->lParam;
            pRect=&pParam->rgrc[0];
        }
        else
        {
            pRect=(LPRECT)msg->lParam;
        }

        if ( ::IsZoomed(msg->hwnd))
        {	// 最大化时，计算当前显示器最适合宽高度
#if 0
            MONITORINFO oMonitor = {};
            oMonitor.cbSize = sizeof(oMonitor);
            ::GetMonitorInfo(::MonitorFromWindow(msg->hwnd, MONITOR_DEFAULTTONEAREST), &oMonitor);
            QRect rcWork(oMonitor.rcWork.left, oMonitor.rcWork.top,
                         oMonitor.rcWork.right - oMonitor.rcWork.left, oMonitor.rcWork.bottom - oMonitor.rcWork.top);
            QRect rcMonitor(oMonitor.rcMonitor.left, oMonitor.rcMonitor.top,
                            oMonitor.rcMonitor.right - oMonitor.rcMonitor.left, oMonitor.rcMonitor.bottom - oMonitor.rcMonitor.top);
            rcWork.translate(-oMonitor.rcMonitor.left, -oMonitor.rcMonitor.top);
#else
            QDesktopWidget * pDesktop = QApplication::desktop();
            QRect rcWork = pDesktop->availableGeometry(this);
            QRect rcMonitor = pDesktop->screenGeometry(this);
            rcWork.translate(-rcMonitor.left(), -rcMonitor.top());
#endif

            pRect->right = pRect->left + rcWork.width();
            pRect->bottom = pRect->top + rcWork.height();
            *result = WVR_REDRAW;
            return true;
        }
        //*result = 0;
        *result = WVR_REDRAW;
        return true;
    }break;
    case WM_NCHITTEST:
    {
        *result = 0;
        //qDebug() << "m_borderWidth:" << m_borderWidth << ",m_bResizeable:" << m_bResizeable;
        const LONG border_width = m_borderWidth;
        RECT winrect;
        GetWindowRect(HWND(winId()), &winrect);

        long x = GET_X_LPARAM(msg->lParam);
        long y = GET_Y_LPARAM(msg->lParam);
        if(m_bResizeable && border_width > 0)
        {
            bool resizeWidth = minimumWidth() != maximumWidth() && !::IsZoomed(msg->hwnd);
            bool resizeHeight = minimumHeight() != maximumHeight() && !::IsZoomed(msg->hwnd);

            if(resizeWidth)
            {
                //left border
                if (x >= winrect.left && x < winrect.left + border_width)
                {
                    *result = HTLEFT;
                }
                //right border
                if (x < winrect.right && x >= winrect.right - border_width)
                {
                    *result = HTRIGHT;
                }
            }
            if(resizeHeight)
            {
                //bottom border
                if (y < winrect.bottom && y >= winrect.bottom - border_width)
                {
                    *result = HTBOTTOM;
                }
                //top border
                if (y >= winrect.top && y < winrect.top + border_width)
                {
                    *result = HTTOP;
                }
            }
            if(resizeWidth && resizeHeight)
            {
                //bottom left corner
                if (x >= winrect.left && x < winrect.left + border_width &&
                        y < winrect.bottom && y >= winrect.bottom - border_width)
                {
                    *result = HTBOTTOMLEFT;
                }
                //bottom right corner
                if (x < winrect.right && x >= winrect.right - border_width &&
                        y < winrect.bottom && y >= winrect.bottom - border_width)
                {
                    *result = HTBOTTOMRIGHT;
                }
                //top left corner
                if (x >= winrect.left && x < winrect.left + border_width &&
                        y >= winrect.top && y < winrect.top + border_width)
                {
                    *result = HTTOPLEFT;
                }
                //top right corner
                if (x < winrect.right && x >= winrect.right - border_width &&
                        y >= winrect.top && y < winrect.top + border_width)
                {
                    *result = HTTOPRIGHT;
                }
            }
        }
        if (0 != *result)
        {
            //qDebug() << "result:" << *result;
            return true;
        }

        //*result still equals 0, that means the cursor locate OUTSIDE the frame area
        //but it may locate in titlebar area
        if(m_titlebar)
        {
            //support highdpi
            double dpr = this->devicePixelRatioF();
            //qDebug() << "devicePixelRatioF:" << devicePixelRatioF();
            QPoint pos = m_titlebar->mapFromGlobal(QPoint(x/dpr,y/dpr));
            if (m_titlebar->rect().contains(pos))
            {
                QWidget* child = m_titlebar->childAt(pos);
                if(nullptr == child // 直接在标题栏
                        || m_whiteList.contains(child) // 在标题栏白名单上
                        )
                {
                    *result = HTCAPTION;
                    //qDebug() << "result:" << *result;
                    return true;
                }
            }
        }
        *result = HTCLIENT;
        return true;
        //return false;
    }break;
    case WM_GETMINMAXINFO:
    {
        LPMINMAXINFO lpMMI = (LPMINMAXINFO) msg->lParam;
#if 0
        MONITORINFO oMonitor = {};
        oMonitor.cbSize = sizeof(oMonitor);
        ::GetMonitorInfo(::MonitorFromWindow(msg->hwnd, MONITOR_DEFAULTTONEAREST), &oMonitor);
        QRect rcWork(oMonitor.rcWork.left, oMonitor.rcWork.top,
                     oMonitor.rcWork.right - oMonitor.rcWork.left, oMonitor.rcWork.bottom - oMonitor.rcWork.top);
        QRect rcMonitor(oMonitor.rcMonitor.left, oMonitor.rcMonitor.top,
                        oMonitor.rcMonitor.right - oMonitor.rcMonitor.left, oMonitor.rcMonitor.bottom - oMonitor.rcMonitor.top);
        rcWork.translate(-oMonitor.rcMonitor.left, -oMonitor.rcMonitor.top);
        qDebug() << "rcWork:" << rcWork << ", rcMonitor:" << rcMonitor;
#else
        QDesktopWidget * pDesktop = QApplication::desktop();
        QRect rcWork = pDesktop->availableGeometry(this);
        QRect rcMonitor = pDesktop->screenGeometry(this);
        rcWork.translate(-rcMonitor.left(), -rcMonitor.top());  // 屏幕坐标一般都是0,但是多屏幕时启动屏幕的左上角不是0
        //qDebug() << "availableGeometry:" << pDesktop->availableGeometry(this) << ",screenGeometry:" << pDesktop->screenGeometry(this);
        //qDebug() << "rcWork:" << rcWork << ", rcMonitor:" << rcMonitor;
#endif
        // 计算最大化时，正确的原点坐标
        // 设置窗口最大化时的宽度、高度
        lpMMI->ptMaxSize.x = rcWork.width();
        lpMMI->ptMaxSize.y = rcWork.height();
        // 设置窗口最大化时x坐标、y坐标，似乎要填入0值
        lpMMI->ptMaxPosition.x	= rcWork.left();
        lpMMI->ptMaxPosition.y	= rcWork.top();
        // 设置窗口最大宽度、高度
        lpMMI->ptMaxTrackSize.x = rcWork.width();
        lpMMI->ptMaxTrackSize.y = rcWork.height();
        // 设置窗口最小宽度、高度
        lpMMI->ptMinTrackSize.x = this->minimumWidth();
        lpMMI->ptMinTrackSize.y = this->minimumHeight();

        return true;
    }break;
#if 0
    case WM_SIZE:
    {
        SIZE szRoundCorner = {3, 3};    // 圆角
#if defined(WIN32) && !defined(UNDER_CE)
        if( !::IsIconic(msg->hwnd) && (szRoundCorner.cx != 0 || szRoundCorner.cy != 0) )
        {
            RECT rcWnd;
            ::GetWindowRect(msg->hwnd, &rcWnd);

            QRect rcWndQt(rcWnd.left, rcWnd.top, rcWnd.right - rcWnd.left, rcWnd.bottom - rcWnd.top);
            rcWndQt.translate(-rcWnd.left, -rcWnd.top);
            rcWnd.left = rcWndQt.left();
            rcWnd.top = rcWndQt.top();
            rcWnd.right = rcWndQt.right();
            rcWnd.bottom = rcWndQt.bottom();

            rcWnd.right += 2;
            rcWnd.bottom += 2;
            HRGN hRgn = ::CreateRoundRectRgn(rcWnd.left, rcWnd.top, rcWnd.right, rcWnd.bottom, szRoundCorner.cx, szRoundCorner.cy);
            ::SetWindowRgn(msg->hwnd, hRgn, TRUE);
            ::DeleteObject(hRgn);
        }
#endif
        return false;
    }break;
#endif
    default:
        return QMainWindow::nativeEvent(eventType, message, result);
    }

    return QMainWindow::nativeEvent(eventType, message, result);
}

#endif //Q_OS_WIN
